package com.gxty.gat1400.interceptor

import com.gxty.gat1400.annotation.RequireAuth
import com.gxty.gat1400.utils.AuthDigestUtil
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@Component
class RequireAuthInterceptor : HandlerInterceptor {
    companion object {
        var gat1400Password: String? = null
    }

    @Value("\${gat1400.password}")
    fun setGat1400Password(password: String?) {
        gat1400Password = password
    }

    // 为了测试 Digest nc 值每次请求增加
    private var nc = 0

    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        // 请求目标为 method of controller,需要进行验证
        if (handler is HandlerMethod) {
            // 方法没有 @RequireAuth 注解,放行
            handler.getMethodAnnotation(RequireAuth::class.java) ?: return true
            // 方法有 @RequireAuth 注解,需要拦截校验
            if (!isAuth(request, response)) {
                // 验证不通过，拦截
                return false
            }
            // 验证通过,放行
            return true
        }
        // 请求目标不是 method of controller, 放行
        return true
    }

    private fun isAuth(req: HttpServletRequest, res: HttpServletResponse): Boolean {
        val authStr = req.getHeader("Authorization")
        if (authStr == null || authStr.length <= 7) {
            // 没有 Authorization 请求头，开启质询
            return challenge(res)
        }
        val authObject = AuthDigestUtil.getAuthInfoObject(authStr)

        // 生成 response 的算法: response = MD5(MD5(username:realm:password):nonce:nc:cnonce:qop:MD5(<request-method>:url))
        // 这里密码固定为 123456, 实际应用可根据用户名查询数据库或缓存获得
        val HA1 = AuthDigestUtil.MD5("${authObject.username}:${authObject.realm}:${gat1400Password}")
        val HD = String.format("${authObject.nonce}:${authObject.nc}:${authObject.cnonce}:${authObject.qop}")
        val HA2 = AuthDigestUtil.MD5("${req.method}:${authObject.uri}")
        val responseValid = AuthDigestUtil.MD5("$HA1:$HD:$HA2")

        // 如果 Authorization 中的 response（浏览器生成的） 与期望的 response（服务器计算的） 相同，则验证通过
        if ((responseValid == authObject.response)) {
            /* 判断 nc 的值，用来防重放攻击 */
            // 判断此次请求的 Authorization 请求头里面的 nc 值是否大于之前保存的 nc 值
            // 大于，替换旧值，然后 return true
            // 否则，return false
            val newNc = authObject.nc!!.toInt(16)
            if (newNc > nc) {
                nc = newNc
                return true
            }
            return false
        }
        // 验证不通过，重复质询
        return challenge(res)
    }

    private fun challenge(res: HttpServletResponse): Boolean {
        // 质询前，重置或删除保存的与该用户关联的 nc 值（nc：nonce计数器，是一个16进制的数值，表示同一nonce下客户端发送出请求的数量）
        // 将 nc 置为初始值 0
        nc = 0
        res.status = 401
        val str = """
            Digest realm="gxty.com", qop="auth", nonce="${AuthDigestUtil.generateNonce()}"
        """.trimIndent()
        res.addHeader("WWW-Authenticate", str)
        return false
    }
}